package com.wmx.wmxredis.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import redis.clients.jedis.*;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis 缓存工具类
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2024/1/24 16:46
 */
@Component
public class RedisCacheUtil {

    private static final Logger log = LoggerFactory.getLogger(RedisCacheUtil.class);

    @Resource
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象，Integer、String、实体类等
     * 1、key 存在时，自动覆盖。
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功；false=设置失败
     */
    public boolean expire(final String key, final long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功；false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据，不存在时 返回 null。
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean delKey(final String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long delKeys(final Collection collection) {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext()) {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   Redis键
     * @param hKey  Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey) {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }


    public void incrementCacheMapValue(String key, String hKey, int v) {
        redisTemplate.opsForHash().increment(key, hKey, v);
    }

    /**
     * 删除Hash中的数据
     *
     * @param key
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hkey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key   Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 模糊查询缓存的key名称。
     * 无论单机部署、集群部署都可以直接使用。虽然使用方便，但是存在性能问题，生产环境不推荐。
     * ！某些环境可能会禁用此命令。
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern) {
        return redisTemplate.keys(pattern);
    }

    //===========scan=========模糊查询========模糊删除key==============./start==========================

    /**
     * 模糊删除key——————适合Redis单机部署
     * 1、生产环境下，keys 存在性能问题，推荐使用 scan 命令查询。
     * <T> T RedisTemplate.execute(RedisCallback<T> action)
     * <pre>
     *     对应Redis命令：scan cursor [MATCH pattern] [COUNT count]
     *     127.0.0.1:6379> scan 0 match *time* count 100
     *          1) "0"
     *          2) 1) "SystemTimer#time4"
     *             2) "SystemTimer#time4#log"
     * </pre>
     *
     * @param pattern ：key 匹配模式，比如 basic*，模糊匹配'basic'字开头的key；*basic* 模糊匹配保护'basic'的key
     * @return ：返回删除的条数
     */
    public long delKeyPatternV1(String pattern) {
        long delCount = 0L;
        long matchCount = 1000L;
        Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keySet = new HashSet<>();
            ScanOptions scanOptions = ScanOptions.scanOptions().match(pattern).count(matchCount).build();
            // 放在try中自动释放cursor
            try (Cursor<byte[]> cursor = connection.scan(scanOptions)) {
                // cursor.getCursorId()：表示当前游标的索引，为0时，表示当前是最后一次，一次对应matchCount条key信息；对应scan命令的第一个返回值;
                // cursor.getPosition()：表示当前游标对应的matchCount条key信息中的第几条，从0开始；对应scan命令第二个返回值中的索引;
                // cursor.hasNext()：表示是否还有下一次
                // cursor.next()：表示当前key的名称
                while (cursor.hasNext()) {
                    byte[] key = cursor.next();
                    keySet.add(new String(key));
                }
            } catch (IOException e) {
                log.error("Redis模糊查询key异常：{}", e);
            }
            return keySet;
        });
        if (!keys.isEmpty()) {
            delCount = redisTemplate.delete(keys);
        }
        log.info("Redis模糊删除Key:{} -> {}", pattern, delCount);
        return delCount;
    }

    /**
     * 模糊删除key——————适合Redis单机部署
     * 1、生产环境下，keys 存在性能问题，推荐使用 scan 命令查询。
     *
     * @param pattern ：key 匹配模式，比如 basic*，模糊匹配'basic'字开头的key；*basic* 模糊匹配保护'basic'的key
     * @return ：返回删除的条数
     */
    public long delKeyPatternV2(String pattern) {
        long delCount = 0L;
        long matchCount = 1000L;
        Set<String> keys = new HashSet<>();
        // 放在try中自动释放cursor
        try (Cursor<byte[]> cursor = redisTemplate.getConnectionFactory().getConnection().scan(ScanOptions.scanOptions().match(pattern).count(matchCount).build())) {
            while (cursor.hasNext()) {
                keys.add(new String(cursor.next()));
            }
        } catch (IOException e) {
            log.error("Redis模糊查询key异常：{}", e);
        }
        if (!keys.isEmpty()) {
            delCount = redisTemplate.delete(keys);
        }
        log.info("Redis模糊删除Key:{} -> {}", pattern, delCount);
        return delCount;
    }

    /**
     * 模糊删除key - 高版本推荐使用下面这种方式，几行即可搞定;
     * 在线异步地批量扫描匹配删除，先SCAN，再批量DEL
     * https://juejin.cn/post/7313242056742354994
     *
     * @see org.springframework.data.redis.cache.RedisCacheWriter#clean
     */
    // private void clean(String cacheName, String keyPattern) {
    //     RedisConnectionFactory redisConnectionFactory = redisTemplate.getConnectionFactory();
    //     RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory, BatchStrategies.scan(1000));
    //     redisCacheWriter.clean(cacheName, keyPattern.getBytes(StandardCharsets.UTF_8));
    // }

    /**
     * Redis 集群部署模糊查询。——————Jedis 客户端操作。
     * 1、不适用Redis VIP 集群部署模式，即虽然Redis是集群部署，但是却使用了虚拟IP，对外只提供了这一个虚拟IP(VIP)
     * 2、集群部署时，无法再直接使用 redisTemplate 的 scan 方法。这里以 Jedis 客户端操作为例，它需要拿到全部的Redis节点，
     * 然后逐个遍历，使用 scan 方法，最好合并每个节点的查询节点。
     *
     * @param pattern ：模糊查询内容，比如 *BASIC_SERVER*
     * @return ：返回模糊查询的key集合
     */
    @NotNull Set<String> scanByPatternForCluster(String pattern) {
        Set<String> keys = new TreeSet<>();
        ScanParams params = new ScanParams();
        params.match(pattern);
        params.count(1000);
        RedisClusterConnection clusterConnection = this.redisTemplate.getConnectionFactory().getClusterConnection();
        // key是 Redis 节点的ip:port值，value是对应的连接池对象
        // 每个 Redis 节点都会对应一个 JedisPool 对象，通过 JedisPool 来管理 Jedis 的申请释放复用等
        Map<String, JedisPool> clusterNodes = ((JedisCluster) clusterConnection.getNativeConnection()).getClusterNodes();
        clusterNodes.forEach((k, jedisPool) -> {
            try (Jedis jedis = jedisPool.getResource()) {
                if (jedis.info("replication").contains("role:slave")) {
                    // 集群中的主节点数据相互独立，从节点的数据与主节点相同，所以不是主节点而是副节点时不处理..
                    return;
                }
                ScanResult<String> scanResult;
                String cursor = ScanParams.SCAN_POINTER_START;
                do {
                    scanResult = jedis.scan(cursor, params);// 对当前节点执行scan命令
                    if (scanResult != null && scanResult.getResult() != null && !scanResult.getResult().isEmpty()) {
                        keys.addAll(scanResult.getResult());
                    }
                    if (scanResult == null) {
                        continue;
                    }
                    // jedis V2.9.0 使用 getStringCursor()方法, V3.1.0 调整为 getCursor()方法.
                    cursor = scanResult.getStringCursor();
                } while (scanResult != null && !ScanParams.SCAN_POINTER_START.equals(cursor));
            }
        });
        return keys;
    }

    //==========scan======模糊查询============模糊删除key==============./end==========================

}
